-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4709: report if local implicits need type annotation #4729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would do a more generic solution that also handles #3253
tests/neg/i4709.scala
Outdated
def test(ctx0: Context) = { | ||
implicit val ctx = { ctx0.settings; ??? } // error | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make it an error message test as well
Memo: both here and in #3253, implicit search fails because it tries to force the type of the member we're typechecking — whether the method is actually implicit or not. In #3253, we produce It seems we should check, instead, if the failure is in implicit resolution or not (not sure how, |
1be9a81
to
7c51b08
Compare
This comment has been minimized.
This comment has been minimized.
Memo for future explorers: if you really really want, it might be possible to find all the symbols involved in a cycle, but at least for now we decided not to try. This would work following the idea of #4385: we'd recording more information on the |
@@ -49,7 +49,8 @@ object NameOps { | |||
} | |||
} | |||
|
|||
implicit class NameDecorator[N <: Name](val name: N) extends AnyVal { | |||
implicit class NameDecorator[N <: Name](val name_ : N) extends AnyVal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can make it private:
- implicit class NameDecorator[N <: Name](val name: N) extends AnyVal {
+ implicit class NameDecorator[N <: Name](private val name: N) extends AnyVal {
import nme._
def testSimple(f: SimpleName => Boolean): Boolean = name match {
@@ -233,12 +233,12 @@ object NameOps {
case nme.clone_ => nme.clone_
}
- def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = {
+ def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): N = {
val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1))
val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1))
- name.likeSpaced(name ++ nme.specializedTypeNames.prefix ++
+ likeSpaced(name ++ nme.specializedTypeNames.prefix ++
methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++
classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix)
class CyclicReference private (val denot: SymDenotation) extends TypeError { | ||
var inImplicitSearch: Boolean = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make this a parameter of the class and then do something like:
def withinImplicitSearch =
if (inImplicitSearch) this
else new CyclicReference(denot, inImplicitSearch = true)
...
} catch {
case ce: CyclicReference =>
throw ce.withinImplicitSearch
}
I'm not the best to come up with method name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new Exception has a different stack trace and other state. I could try to copy the various relevant fields (I spent some time investigating), but that seems more fragile than having one mutable field.
} | ||
} catch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to wrap the whole method? Can't you do:
val result =
try new ImplicitSearch(pt, argument, pos).bestImplicit(contextual = true)
catch {
case ce: CyclicReference =>
...
}
val unsafeFlags = cycleSym.flagsUNSAFE | ||
val isMethod = unsafeFlags.is(Method) | ||
val isVal = !isMethod && cycleSym.isTerm | ||
val isImplicit = unsafeFlags.is(Implicit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused?
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise LGTM. Sorry for the late review @Blaisorblade
else if (isVal) | ||
RecursiveValueNeedsResultType(tree.name, cycleSym) | ||
else | ||
errorMsg(cx.outer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think we should remove all of this if tree.name
is unused and we only use the cycleSym
val explanation = | ||
hl"""The definition of `${value}` is recursive and you need to specify its type. | ||
hl"""The definition of `${cycleSym}` is recursive and you need to specify its type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: here and in some of the error messages above s/${cycleSym}
/$cycleSym
@@ -1260,26 +1260,27 @@ object messages { | |||
|""".stripMargin | |||
} | |||
|
|||
case class OverloadedOrRecursiveMethodNeedsResultType(method: Names.TermName)(implicit ctx: Context) | |||
case class OverloadedOrRecursiveMethodNeedsResultType(methodName: Names.TermName, cycleSym: Symbol)(implicit ctx: Context) | |||
extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) { | |||
val kind = "Syntax" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, and we should probably classify OverloadedOrRecursiveMethodNeedsResultType
, RecursiveValueNeedsResultType
, TermMemberNeedsResultTypeForImplicitSearch
, CyclicReferenceInvolvingImplicit
and CyclicReferenceInvolving
as Cyclic
errors not Syntax
Tentative advice copied from `CyclicReferenceInvolvingImplicit`.
…better - Record in CyclicReference if it's emitted during implicit search. - Before checking if the error is on an implicit, check if we have an error during implicit search that happens while inferring a return type; that can happen whether the member we're typechecking is implicit or not, and needs the same cure. - Add ErrorMessagesTests as requested This improves the error message, and also puts more effort into diagnosing the cycle: we now use similar logic as for recursive/overloaded members to check we're indeed dealing with a (potentially) recursive implicit. Remaining `CyclicReferenceInvolvingImplicit` are unexpected and might involve bugs, so make the error message and explanation more tentative, following `CyclicReferenceInvolving`. *However*, this does not yet handle mutually recursive methods in a reasonable way.
Make reports about cycleSym not tree.name. We need then to use flagsUNSAFE, see description. * Store cycleSym in all relevant errors * Mention cycleSym instead of tree.name in errors Tests pass, and have the correct expectations both for `tree.name` and `cycleSym.name`, tho we just show `cycleSym.name` and `tree.name` is less accurate. But at this point, I might be able to stop using `tree.name` altogether.
As suggested in review.
All comments addressed, so merging. |
Fix #4709 and #3253, and revisit fixes to #2001.